//
// Copyright (c) Microsoft Corporation.  All rights reserved.
//
//
// Use of this sample source code is subject to the terms of the Microsoft
// license agreement under which you licensed this sample source code. If
// you did not accept the terms of the license agreement, you are not
// authorized to use this sample source code. For the terms of the license,
// please see the license agreement between you and Microsoft or, if applicable,
// see the LICENSE.RTF on your install media or the root of your tools installation.
// THE SAMPLE SOURCE CODE IS PROVIDED "AS IS", WITH NO WARRANTIES OR INDEMNITIES.
//
//
// (C) Copyright 2006 Marvell International Ltd.
// All Rights Reserved
//
/******************************************************************************
**
**  COPYRIGHT (C) 2004 Intel Corporation.
**
**  This software as well as the software described in it is furnished under
**  license and may only be used or copied in accordance with the terms of the
**  license. The information in this file is furnished for informational use
**  only, is subject to change without notice, and should not be construed as
**  a commitment by Intel Corporation. Intel Corporation assumes no
**  responsibility or liability for any errors or inaccuracies that may appear
**  in this document or any software that may be provided in association with
**  this document. 
**  Except as permitted by such license, no part of this document may be 
**  reproduced, stored in a retrieval system, or transmitted in any form or by
**  any means without the express written consent of Intel Corporation. 
**
**  FILENAME:  xllp_lcd_cpu.c
**
**  PURPOSE:   common source file for XLLP LCD controller. 
**
*******************************************************************************/

#include "xllp_defs.h"

#include "xllp_ost.h"
#include "xllp_lcd.h"
#include "xllp_lcd_cpu.h"
#include "ipm_xllp_pm_dvfm.h"
#include <windows.h>

static void TFTInitController(pXllpLCD);

#define MAX_LOOP 100000

void WaitForBranch(XLLP_LCD_REGISTERS_T *regs)
{
    int i = 0;

    while(regs->fbr0 & XLLP_FBR_BRA 
         || regs->fbr2 & XLLP_FBR_BRA
         || regs->fbr3 & XLLP_FBR_BRA
         || regs->fbr4 & XLLP_FBR_BRA)
    {
        if (i++ > MAX_LOOP)
        {
            NKDbgPrintfW(TEXT("ERROR!!! WaitForBranch exceeded max loops\r\n"));
            return;
        }
    }
}

XLLP_STATUS_T TFTInit(P_XLLP_LCD_T pXllpLCD)
{
	XLLP_STATUS_T status;

    pXllpLCD->overlay2 = NULL;
	status = pXllpLCD->panel->tft.init_pins(pXllpLCD);
	LCDInitDMA(pXllpLCD);
	TFTInitController(pXllpLCD);
    if (pXllpLCD->panel->tft.post_init) 
        pXllpLCD->panel->tft.post_init(pXllpLCD);
	
	return status;
}

void TFTResume(P_XLLP_LCD_T pXllpLCD)
{
	pXllpLCD->panel->tft.init_pins(pXllpLCD);
	TFTInitController(pXllpLCD);
}

static void TFTInitController(P_XLLP_LCD_T pXllpLCD)
{
	tft_timing_t *timing = &pXllpLCD->panel->tft.timing;
	tft_polarity_t *polarity = &pXllpLCD->panel->tft.polarity;
    XLLP_LCD_REGISTERS_T* pLCDRegs;
    XllpClockEnable((XLLP_CLKMGR_T*)(pXllpLCD->CLKMan), XLLP_CLK_LCD, XLLP_FALSE);

	LCDInitControllerCommon(pXllpLCD);

    pLCDRegs = (XLLP_LCD_REGISTERS_T*)pXllpLCD->LCDC;
    pLCDRegs->lccr6 = 0;
    pLCDRegs->fbr0 = 0;

	pLCDRegs->lccr1 |= XLLP_LCCR1_HSW(timing->HSW) 
		| XLLP_LCCR1_ELW(timing->ELW) 
		| XLLP_LCCR1_BLW(timing->BLW);

	pLCDRegs->lccr2 |= XLLP_LCCR2_VSW(timing->VSW) 
		| XLLP_LCCR2_EFW(timing->EFW)  
		| XLLP_LCCR2_BFW(timing->BFW);

	pLCDRegs->lccr3 |= XLLP_LCCR3_OEP(polarity->OEP)
		| XLLP_LCCR3_PCP(polarity->PCP)
		| XLLP_LCCR3_HSP(polarity->HSP) 
		| XLLP_LCCR3_VSP(polarity->HSP);
			
	TFTUpdateClock(pXllpLCD, HSS_AUTO_DETECT);

    // Load the contents of FDADR0 with the physical address of this frame descriptor
    pLCDRegs->fdadr0 = XLLP_FDADR_DESCADDR(pXllpLCD->frameDescriptorCh0fd1->FDADR);
	pLCDRegs->lccr0 |= XLLP_LCCR0_ENB;
    XllpClockEnable((XLLP_CLKMGR_T*)(pXllpLCD->CLKMan), XLLP_CLK_LCD, XLLP_TRUE);
}

void TFTUpdateClock(P_XLLP_LCD_T pXllpLCD, XLLP_UINT32_T hss)
{
	XLLP_UINT32_T pcd;
	XLLP_LCD_REGISTERS_T* pReg;
	tft_timing_t *timing = &(pXllpLCD->panel->tft.timing);

	if (hss == HSS_AUTO_DETECT) {
		hss = ((((XLLP_CLKMGR_T*)(pXllpLCD->CLKMan))->acsr)&0xc000)>>14;
	}
	
	switch (hss) 
	{
		case XLLP_PM_ACCR_HSS_D0CS:
			pcd = timing->PCD[PCD_D0CS];
		break;
		case HSS_104M:
			pcd = timing->PCD[PCD_104M];
			break;
		case HSS_156M:
			pcd = timing->PCD[PCD_156M];
			break;
		case HSS_208M:
			pcd = timing->PCD[PCD_208M];
			break;						
		default:
			pcd = timing->PCD[PCD_208M];
			break;
	}
	pReg = (XLLP_LCD_REGISTERS_T*)(pXllpLCD->LCDC);
	pReg->lccr4 |= XLLP_LCCR4_PIX_DIV_EQ;
	pReg->lccr3 = (pReg->lccr3 & (~XLLP_LCCR3_PCD_MASK)) | pcd;
	return;
}

void TFTFlip(P_XLLP_LCD_T pXllpLCD, XLLP_UINT32_T frame_phy_addr)
{
	XLLP_LCD_REGISTERS_T* lcd_reg = (XLLP_LCD_REGISTERS_T*)pXllpLCD->LCDC;


	LCD_FRAME_DESCRIPTOR *dma_desc = pXllpLCD->frameDescriptorCh0fd1;
        WaitForBranch(lcd_reg);
	dma_desc->FSADR = XLLP_FSADR_SRCADDR(frame_phy_addr);

	pXllpLCD->_FRAME_BUFFER_BASE_PHYSICAL = frame_phy_addr;

	// need reload DMA descriptor
	// descriptor is reloaded after last frame finished, 
	// so Hardware always wait for VBlank
	lcd_reg->fbr0 = XLLP_FDADR_DESCADDR(dma_desc->FDADR) | (XLLP_FBR_BRA | XLLP_FBR_BINT);
}

void TFTEnableEOF(LCD_FRAME_DESCRIPTOR* desc)
{
    desc->LDCMD |= XLLP_LDCMD_EOFINT;
}

void TFTDisableEOF(LCD_FRAME_DESCRIPTOR* desc)
{
    desc->LDCMD &= ~XLLP_LDCMD_EOFINT;
}

XLLP_STATUS_T TFT_Overlay2_Enable(P_XLLP_LCD_T pXllpLCD, P_XLLP_OVERLAY_T pXllpOverlay)
{
    XLLP_STATUS_T status = 0;
    XLLP_LCD_REGISTERS_T* pLCDRegs;
    // unsigned int StatMask = 0; 

    pLCDRegs = (XLLP_LCD_REGISTERS_T*) pXllpLCD->LCDC; 
	pXllpLCD->overlay2 = pXllpOverlay;

    if ((pLCDRegs->ovl2c1) & XLLP_OVL2C1_O2EN)
    {
        return(status);
    }

    WaitForBranch(pLCDRegs);

    // Change baseframe pixel format to reduce memory bus bandwidth
    if (pXllpOverlay->DegradeBaseFrame) 
    {
        XllpLCDSuspend(pXllpLCD, Suspend_Graceful);
        pXllpOverlay->TmpBPP = pXllpLCD->BPP;
        pXllpLCD->BPP = BPP_1;
        XllpLCDResume(pXllpLCD);
    }

	LCDInitOverlay2DMA(pXllpLCD, pXllpOverlay);
    
    // FBRx is cleared and is not used.
    pLCDRegs->fbr2 = 0;
    pLCDRegs->fbr3 = 0;
    pLCDRegs->fbr4 = 0;

    pLCDRegs->fdadr2 = XLLP_FDADR_DESCADDR(pXllpLCD->_DMA_CHANNEL_2_Y_FRAME_DESCRIPTOR_BASE_PHYSICAL);
    if (pXllpOverlay->Format != FORMAT_RGB)
    {
        pXllpOverlay->OverlayBPP = 0x2; // This is the reset value. Let it be.
        if (pXllpOverlay->Format != FORMAT_PACKED_444)
        {
            pLCDRegs->fdadr3 = XLLP_FDADR_DESCADDR(pXllpLCD->_DMA_CHANNEL_3_Cb_FRAME_DESCRIPTOR_BASE_PHYSICAL);
            pLCDRegs->fdadr4 = XLLP_FDADR_DESCADDR(pXllpLCD->_DMA_CHANNEL_4_Cr_FRAME_DESCRIPTOR_BASE_PHYSICAL);
        }
    }

    pLCDRegs->ovl2c2 = XLLP_OVL2C2_FOR(pXllpOverlay->Format) | XLLP_OVL2C2_O2YPOS(pXllpOverlay->Y_Position) | XLLP_OVL2C2_O2XPOS(pXllpOverlay->X_Position);
    pLCDRegs->ovl2c1 = XLLP_OVL2C1_O2EN | XLLP_OVL2C1_BPP2(pXllpOverlay->OverlayBPP) | XLLP_OVL2C1_LPO2(pXllpOverlay->OverlayHeight-1) | XLLP_OVL2C1_PPL2(pXllpOverlay->OverlayWidth-1);
    
    return(status);
} // XllpLCD_Overlay2_Enable

void TFT_Overlay2_Disable(P_XLLP_LCD_T pXllpLCD)
{
    XLLP_LCD_REGISTERS_T* pLCDRegs;
    unsigned int OVL2C1;
    unsigned int OVL2C2;
    unsigned int OL2Format;
    unsigned int StatMask = 0; 

	P_XLLP_OVERLAY_T pXllpOverlay = pXllpLCD->overlay2;

    pLCDRegs = (XLLP_LCD_REGISTERS_T*) pXllpLCD->LCDC; 

    OVL2C1 = pLCDRegs->ovl2c1;
    if (!(OVL2C1 & XLLP_OVL2C1_O2EN))
    {
        return;
    }
    
    WaitForBranch(pLCDRegs);

    // Get current overlay format 
    OVL2C2 = pLCDRegs->ovl2c2;
    OL2Format = (OVL2C2 >> 20) & 7;
    StatMask = XLLP_LCSR1_BS2;
    if (OL2Format != FORMAT_RGB)
        StatMask |= (XLLP_LCSR1_BS3 | XLLP_LCSR1_BS4);
        
    pLCDRegs->lcsr1 = StatMask;
    pLCDRegs->ovl2c1 &= ~XLLP_OVL2C1_O2EN; // Clear the enable bit

    // Wait for DMA branch taken status (BSx bits to be set).
    // This is per HW Eng. workaround described in Tibet sighting 49219.
    // Note that testing indicates the loading of FBRx avoids the hardware problem.
    // Not loading FBRx and just waiting for 50ms does not avoid the problem.

    if ((OL2Format != FORMAT_RGB)&&(OL2Format != FORMAT_PACKED_444))
    {
        pLCDRegs->fbr3 = XLLP_FDADR_DESCADDR(pXllpLCD->frameDescriptorCh3_YCbCr_Cb->FDADR) | XLLP_FBR_BRA | XLLP_FBR_BINT;
        pLCDRegs->fbr4 = XLLP_FDADR_DESCADDR(pXllpLCD->frameDescriptorCh4_YCbCr_Cr->FDADR) | XLLP_FBR_BRA | XLLP_FBR_BINT;
    }
    pLCDRegs->fbr2 = XLLP_FDADR_DESCADDR(pXllpLCD->frameDescriptorCh2_YCbCr_Y->FDADR) | (XLLP_FBR_BRA | XLLP_FBR_BINT);

    if (pXllpOverlay && pXllpOverlay->DegradeBaseFrame) 
    {
        // Restore BaseFrame Pixel Format
        XllpLCDSuspend(pXllpLCD, Suspend_Graceful);
        pXllpLCD->PixelDataFormat = PDFOR_00;
        pXllpLCD->BPP = pXllpOverlay->TmpBPP;
        XllpLCDResume(pXllpLCD);
    }

	pXllpLCD->overlay2 = NULL;

    return;
} // XllpLCD_Overlay2_Disable


XLLP_STATUS_T TFT_Overlay2_DynChange(P_XLLP_LCD_T pXllpLCD, XLLP_UINT32_T changeFlag)
{
    XLLP_LCD_REGISTERS_T* pLCDRegs;
	P_XLLP_OVERLAY_T pXllpOverlay = pXllpLCD->overlay2;

    pLCDRegs = (XLLP_LCD_REGISTERS_T*) pXllpLCD->LCDC;
    

    if (!(pLCDRegs->ovl2c1 & XLLP_OVL2C1_O2EN) && !pXllpLCD->overlay2) 
        return XLLP_STATUS_INVALID_STATE;
        
    WaitForBranch(pLCDRegs);

    if (changeFlag & Change_Size_Position) 
    {
        pLCDRegs->ovl2c2 = (XLLP_OVL2C2_FOR(pXllpOverlay->Format) | XLLP_OVL2C2_O2YPOS(pXllpOverlay->Y_Position) | XLLP_OVL2C2_O2XPOS(pXllpOverlay->X_Position));
        pLCDRegs->ovl2c1 = (XLLP_OVL2C1_O2EN | XLLP_OVL2C1_BPP2(pXllpOverlay->OverlayBPP) | XLLP_OVL2C1_LPO2(pXllpOverlay->OverlayHeight-1) | XLLP_OVL2C1_PPL2(pXllpOverlay->OverlayWidth-1));
    } 
    
    if (changeFlag & Change_FrameBuffer) 
    {
        pXllpLCD->frameDescriptorCh2_YCbCr_Y->FSADR = XLLP_FSADR_SRCADDR(pXllpLCD->_OVERLAY2_Y_CHANNEL_BASE_PHYSICAL);                // frame buffer
        pXllpLCD->frameDescriptorCh3_YCbCr_Cb->FSADR = XLLP_FSADR_SRCADDR(pXllpLCD->_OVERLAY2_Cb_CHANNEL_BASE_PHYSICAL);               // frame buffer
        pXllpLCD->frameDescriptorCh4_YCbCr_Cr->FSADR = XLLP_FSADR_SRCADDR(pXllpLCD->_OVERLAY2_Cr_CHANNEL_BASE_PHYSICAL);               // frame buffer
    }
    
    pLCDRegs->fbr2 = pXllpLCD->_DMA_CHANNEL_2_Y_FRAME_DESCRIPTOR_BASE_PHYSICAL | XLLP_FBR_BRA ;
    if ((pXllpOverlay->Format != FORMAT_RGB)&&(pXllpOverlay->Format != FORMAT_PACKED_444))
    {
        pLCDRegs->fbr3 = pXllpLCD->_DMA_CHANNEL_3_Cb_FRAME_DESCRIPTOR_BASE_PHYSICAL | XLLP_FBR_BRA ;
        pLCDRegs->fbr4 = pXllpLCD->_DMA_CHANNEL_4_Cr_FRAME_DESCRIPTOR_BASE_PHYSICAL | XLLP_FBR_BRA ;
    }
    
    return XLLP_STATUS_SUCCESS;
}

// The macro to downsample RGB16 to RGB555
#define rgb16to15(n) (XLLP_UINT16_T)((n&0x1f)|((n&0xffc0)>>1))
            
static XLLP_STATUS_T MiniLCDEncodeRGB565(P_XLLP_MINI_LCD_T pXllpMiniLCD, 
                                  XLLP_UINT32_T FrameSource, 
                                  P_XLLP_UINT32_T pByteCount,
                                  XLLP_UINT16_T width,
                                  XLLP_UINT16_T height)
{
    XLLP_UINT16_T lastValue, currentValue, count;
	int x, y;
    XLLP_UINT32_T m, n;
    XLLP_UINT32_T quota;
    P_XLLP_UINT16_T s;
    P_XLLP_UINT16_T d;
    
    if ((FrameSource&1)||(pXllpMiniLCD->SRAM_virt&7)) 
		return XLLP_STATUS_WRONG_PARAMETER;
    
    s = (P_XLLP_UINT16_T)FrameSource;
    d = (P_XLLP_UINT16_T)pXllpMiniLCD->SRAM_virt;
    m = n = 0;
    quota = pXllpMiniLCD->SRAMQuota - 1;
    for(y = 0; y < height; y++) 
    {
        if (n>quota) 
			return XLLP_STATUS_NO_RESOURCES;
        lastValue = d[n++] = rgb16to15(s[m]);
		m++;
        count = 0;
        for(x = 1; x < width - 1; x++)
        {
            currentValue = rgb16to15(s[m]);
			m++;
            if (currentValue == lastValue) 
                count++;
            else 
            {
                if (count>0) 
                {
                    if (n>quota) 
						return XLLP_STATUS_NO_RESOURCES;
                    d[n++] = count | 0x8000;
	                count = 0;
                }
                if (n>quota) 
					return XLLP_STATUS_NO_RESOURCES;
                lastValue = d[n++] = currentValue;
            }
        }

        if (count>0) 
        {
            if (n>quota) 
				return XLLP_STATUS_NO_RESOURCES;
            d[n++] = count | 0x8000;
        }

		d[n++] = rgb16to15(s[m]);
		m++;

        if (n>quota) 
			return XLLP_STATUS_NO_RESOURCES;       
        d[n++] = 0x9001;
    }
   
    d[n - 1] = 0xa001;
    // Padding for multiple of 64-bit in length 
    while (n & 7)
    {
        if (n > quota) 
			return XLLP_STATUS_NO_RESOURCES;
        d[n++] = 0xf001;
    }
    *pByteCount = n;            
    return XLLP_STATUS_SUCCESS;
}

XLLP_STATUS_T XllpMiniLCDEncode(P_XLLP_MINI_LCD_T pXllpMiniLCD, 
					   XLLP_UINT32_T FrameSource, 
					   P_XLLP_UINT32_T pByteCount)
{
	// only support RGB565 by now
	return MiniLCDEncodeRGB565(pXllpMiniLCD, FrameSource, pByteCount, 
							   (XLLP_UINT16_T)pXllpMiniLCD->panel->width,
							   (XLLP_UINT16_T)pXllpMiniLCD->panel->height);
}

void XllpMiniLCDEnable(P_XLLP_MINI_LCD_T pXllpMiniLCD)
{
	XLLP_MINILCD_REGISTERS_T* p_MLCDRegs;
	tft_timing_t* timing = &pXllpMiniLCD->panel->timing;
	tft_polarity_t* polarity = &pXllpMiniLCD->panel->polarity;	

	XllpClockEnable((XLLP_CLKMGR_T*)(pXllpMiniLCD->CLKMan), XLLP_CLK_MINI_LCD, XLLP_TRUE);

	p_MLCDRegs = (XLLP_MINILCD_REGISTERS_T*)(pXllpMiniLCD->MLCD);
	p_MLCDRegs->mlccr0 |= XLLP_MLCCR0_OEP(polarity->OEP)
		| XLLP_MLCCR0_PCP(polarity->PCP)
		| XLLP_MLCCR0_HSP(polarity->HSP) 
		| XLLP_MLCCR0_VSP(polarity->HSP)
		| XLLP_MLCCR0_PCD(timing->PCD[PCD_MLCD]);

	p_MLCDRegs->mlccr1 = XLLP_MLCCR1_HSW(timing->HSW) 
		| XLLP_MLCCR1_ELW(timing->ELW) 
		| XLLP_MLCCR1_BLW(timing->BLW)
		| XLLP_MLCCR1_PPL(pXllpMiniLCD->panel->width - 1);
			
	p_MLCDRegs->mlccr2 |= XLLP_MLCCR2_VSW(timing->VSW) 
		| XLLP_MLCCR2_EFW(timing->EFW)  
		| XLLP_MLCCR2_BFW(timing->BFW)
		| XLLP_MLCCR2_LPP(pXllpMiniLCD->panel->height - 1);

	p_MLCDRegs->mlsadd = pXllpMiniLCD->SRAM_phys;
	pXllpMiniLCD->panel->init_mini_lcd_pins(pXllpMiniLCD, 1);

	return;
}

void XllpMiniLCDDisable(P_XLLP_MINI_LCD_T pXllpMiniLCD)
{
	pXllpMiniLCD->panel->init_mini_lcd_pins(pXllpMiniLCD, 0);
	XllpClockEnable((XLLP_CLKMGR_T*)(pXllpMiniLCD->CLKMan), XLLP_CLK_MINI_LCD, XLLP_FALSE);
	return;
}